
#if !defined(GLF_PLATFORM_WIN32) && !defined(GLF_PLATFORM_WP8)
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>

typedef int SOCKET;

#define ENOTINITIALISED 0x10093

int SocketGetLastError() {
	return errno;
}

int closesocket(SOCKET h) {
	return close(h);
}

namespace glf {

	NetError Socket::Init() {
		return NETERROR_NONE;
	}
	NetError Socket::Deinit() {
		return NETERROR_NONE;
	}

}

#endif

namespace glf {

	std::string Socket::GetHostName()
	{
		char buffer[256];
		gethostname(buffer, 256);
		return buffer;
	}
	
	AddrIp4 Socket::MakeAddr(const char *ip, int port)
	{
		AddrIp4 addr;
		addr.Addr.ulongAddr = ntohl(inet_addr(ip));
		addr.port = port;
		return addr;
	}

	const char *Socket::GetAddrIP(const AddrIp4 &addr)
	{
		in_addr ia;
		ia.s_addr = htonl(addr.Addr.ulongAddr);
		return inet_ntoa(ia);
	}

	struct Socket::Impl {
		Socket& mSocket;
		SOCKET mWinSocket;
		SOCKET mConnectedSockets[64];

		Impl(Socket& socket) :
		mSocket(socket) {

			mWinSocket = -1;
			for(size_t i = 0; i < GLF_ASIZEOF(mConnectedSockets); ++i) {
				mConnectedSockets[i] = -1;
			}
		}

		~Impl() {
			Close();
		}		

		void Close() {
			mSocket.mIsOpened = false;

			if(mWinSocket >= 0)  {
				for(size_t i = 0; i < GLF_ASIZEOF(mConnectedSockets); ++i) {
					if(mConnectedSockets[i] != -1) {
						closesocket(mConnectedSockets[i]);
						mConnectedSockets[i] = -1;
					}
				}
				if (mWinSocket != -1) {
					closesocket(mWinSocket);
					mWinSocket = -1;
				}
			}
		}

		bool Open(Type type, Proto protocol, Option option) {
			Close();

			int soType		= type == TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM;
			int soProtocol	= 0 ;//protocol == PROTOCOL_UDP ? IPPROTO_UDP : IPPROTO_TCP;

			mWinSocket = socket(PF_INET, soType, soProtocol);

			if(mWinSocket) {
				SetOptions(option);
			}

			return mWinSocket != 0;
		}

		bool Bind(const AddrIp4& addr) {
			sockaddr_in socketAddr;

			socketAddr.sin_family		= AF_INET;
			socketAddr.sin_port			= htons(addr.port);
			socketAddr.sin_addr.s_addr	= htonl(addr.Addr.ulongAddr);

			int result = bind(mWinSocket, reinterpret_cast<sockaddr*>(&socketAddr), sizeof(socketAddr));
			/*int error =*/ SocketGetLastError();

			return result == 0;
		}

		bool Connect(const AddrIp4& addr) {
			sockaddr_in socketAddr;
			socketAddr.sin_family = AF_INET;
			socketAddr.sin_port = htons(addr.port);
			socketAddr.sin_addr.s_addr = htonl(addr.Addr.ulongAddr);

			int result = connect(mWinSocket, reinterpret_cast<sockaddr*>(&socketAddr), sizeof(socketAddr));

			int error = SocketGetLastError();

			if (error == EISCONN)
				return true;

			return result == 0;
		}

		bool Listen(int backlog) {
			int r = listen(mWinSocket, backlog);

			return r == 0;
		}

		int FindFreeSocket() {
			for(size_t i = 0; i < GLF_ASIZEOF(mConnectedSockets); ++i) {
				if(mConnectedSockets[i] == -1) {
					return i;
				}
			}
			return -1;
		}

		bool Reject(int id) {
			if(id >= 0 && mConnectedSockets[id] >= 0) {
				/*int ret =*/ closesocket(mConnectedSockets[id]);
				mConnectedSockets[id] = -1;
				return true;
			}

			return false;
		}

		int Accept(AddrIp4& addr) {
			int socketIdx = FindFreeSocket();

			if (socketIdx != -1) {
				sockaddr_in socketAddr;
				socklen_t socklen = sizeof(socketAddr);

				SOCKET socket = accept(mWinSocket, reinterpret_cast<sockaddr*>(&socketAddr), &socklen);

				if (socket != -1) {
					mConnectedSockets[socketIdx] = socket;
					addr.port		= ntohs(socketAddr.sin_port);
					addr.Addr.ulongAddr	= ntohl(socketAddr.sin_addr.s_addr);
					return socketIdx;
				}
			}

			return -1;
		}

		bool SetOptions(Option options) {
#if GLF_PLATFORM_WIN32==0 && GLF_PLATFORM_WP8==0
			int optval;
#else
			char optval;
#endif
			int ret = 0;

			if (mSocket.mType == TYPE_DATAGRAM) {
				optval = TestFlag(options, OPTION_BROADCAST) ? 1 : 0;
				ret = setsockopt(mWinSocket, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval));
			}

			/*int error =*/ SocketGetLastError();

			if (ret >= 0) {
				optval = TestFlag(options, OPTION_REUSEADDR) ? 1 : 0;
				ret = setsockopt(mWinSocket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
			}

			if(ret >= 0) {
#if GLF_PLATFORM_WIN32==0 && GLF_PLATFORM_WP8==0
				int mode = fcntl(mWinSocket, F_GETFL, 0);
				if (TestFlag(options, OPTION_BLOCKING))
					mode &= ~O_NONBLOCK;
				else
					mode |= O_NONBLOCK;				
				fcntl(mWinSocket, F_SETFL, mode);

#else
				DWORD nonBlocking = TestFlag(options, OPTION_BLOCKING) ? 0 : 1;
				if(ioctlsocket(mWinSocket, FIONBIO, &nonBlocking) != 0) {
					ret = -1;
				}
#endif
			}

			if (ret >= 0 && mSocket.mType == TYPE_STREAM) {
				optval = TestFlag(options, OPTION_NODELAY) ? 1 : 0;
				ret = setsockopt(mWinSocket, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval));
			}

			if (ret < 0) {
				mSocket.mLastError = NETERROR_UNKNOWN;
				return false;
			}

			return true;
		}


		int Send(const void* buf, int size, int id) {
			sockaddr_in socketAddr;
			socketAddr.sin_family = AF_INET;

			int flag = 0;

			const int s = mConnectedSockets[id] != -1 ? mConnectedSockets[id] : mWinSocket;
			int bytesSent = send(s, static_cast<const char*>(buf), size, flag);

			if (bytesSent < 0)
				mSocket.mLastError = NETERROR_UNKNOWN;				

			return bytesSent;
		}

		int SendTo(const AddrIp4& addr, const void* buf, int size) {
			sockaddr_in socketAddr;

			socketAddr.sin_family		= AF_INET;
			socketAddr.sin_addr.s_addr= htonl(addr.Addr.ulongAddr);
			socketAddr.sin_port		= htons(addr.port);

			int bytesSent = sendto(mWinSocket, static_cast<const char*>(buf), size, 0, reinterpret_cast<sockaddr*>(&socketAddr), sizeof(socketAddr));

			if(bytesSent < 0) {

				if(bytesSent == ENOTINITIALISED) {
					mSocket.mLastError = NETERROR_NOTINITIALIZED;
				} else {
					mSocket.mLastError = NETERROR_UNKNOWN;
				}
			}

			return bytesSent;
		}

		int Receive(void* buf, int size, int id) {
			int flags = 0;

			const int s = mConnectedSockets[id] != -1 ? mConnectedSockets[id] : mWinSocket;
			int bytesReceived = recv(s, static_cast<char*>(buf), size, flags);		

			if (bytesReceived == 0) {
				// If the connection has been gracefully closed, the return value is zero.	
				mSocket.mLastError = NETERROR_CLOSED;
			}
			else if (bytesReceived < 0) {
				const int error = SocketGetLastError();
				if (error == EWOULDBLOCK)
					//no error...
					bytesReceived = 0;
				else
					mSocket.mLastError = NETERROR_UNKNOWN;
			}

			return bytesReceived;
		}

		int ReceiveFrom(AddrIp4& addr, void* buf, int size) {
			sockaddr_in socketAddr;
			socklen_t saLen = sizeof(socketAddr);

			int bytesReceived = recvfrom(mWinSocket, static_cast<char*>(buf), size, 0, reinterpret_cast<sockaddr*>(&socketAddr), &saLen);

			int error = SocketGetLastError();

			if (bytesReceived < 0)  {
				if(error == EWOULDBLOCK) {
					//no error...
					bytesReceived = 0;
				} else {
					mSocket.mLastError = NETERROR_UNKNOWN;
					//dbgprint("recvfrom() failed (errno=%d) (ret=0x%x)\n", GetSocketError(), ret);
				}
			} else {
				//printf("");
			}

			addr.Addr.ulongAddr	= ntohl(socketAddr.sin_addr.s_addr); //get binded address
			addr.port			= ntohs(socketAddr.sin_port); //get binded port

			return bytesReceived;	
		}

		bool GetAddress(AddrIp4& addr) {
			sockaddr_in sa;
			socklen_t saLen = sizeof(sa);

			int ret = getsockname(mWinSocket, (sockaddr *)&sa, &saLen);

			if (ret < 0) {
				mSocket.mLastError = NETERROR_UNKNOWN;
				//dbgprint("SOCKET - getsockname() failed (errno=%d) (ret=0x%x)\n", GetSocketError(), ret);
				return false;
			}

			addr.Addr.ulongAddr = 0; //sa.
			addr.port = ntohs(sa.sin_port);

			return true;
		}

		bool IsReadable(int id) const
		{
			const int sock = mConnectedSockets[id] != -1 ? mConnectedSockets[id] : mWinSocket;
#if defined(GLF_PLATFORM_CTR)
			s32 readVal;
			const int LENGTH = 1;
			static char buffer[LENGTH];
			readVal = nn::socket::Recv(sock, buffer, LENGTH, nn::socket::MSG_PEEK|nn::socket::MSG_DONTWAIT); //MSG_PEEK ==data is received by the state is not changed, and the same data can be received again

			if (readVal==nn::socket::EAGAIN)
				return false;

			if (readVal>0)
			{
				//		GLF_NETINTEFRFACE_VERBOSE_PRINT("Received messages %d bytes",readVal);
			}
			else
				switch(readVal)
			{
				case 0:
					GLF_NETINTEFRFACE_VERBOSE_PRINT("For stream sockets (SOCK_STREAM), this indicates that the socket cannot accept any more messages because the remote host has finished sending messages. For datagram sockets (SOCK_DGRAM), this function does not return 0.");
					break;
				case nn::socket::ENETRESET:
					GLF_NETINTEFRFACE_VERBOSE_PRINT("Socket library is not initialized.");
					break;
				case nn::socket::EBADF:
					GLF_NETINTEFRFACE_VERBOSE_PRINT("Invalid socket descriptor.");
					break;
				case nn::socket::EAGAIN:
					GLF_NETINTEFRFACE_VERBOSE_PRINT("There is no data or out-of-band data waiting to be received, even when O_NONBLOCK is set in the socket descriptor (or when the MSG_DONTWAIT flag is set).");
					return false;
				case nn::socket::EINVAL:
					GLF_NETINTEFRFACE_VERBOSE_PRINT("Invalid processing.");
					break;
				case nn::socket::EOPNOTSUPP:
					GLF_NETINTEFRFACE_VERBOSE_PRINT("Process not supported.");
					break;
				case nn::socket::ENOTCONN:
					GLF_NETINTEFRFACE_VERBOSE_PRINT("Not connected.");
					break;
				case nn::socket::ECONNRESET:
					GLF_NETINTEFRFACE_VERBOSE_PRINT("The connection was reset.");
					readVal=1;
					break;
				case nn::socket::EINTR:
					GLF_NETINTEFRFACE_VERBOSE_PRINT("Suspended.");
					break;
				case nn::socket::ETIMEDOUT:
					GLF_NETINTEFRFACE_VERBOSE_PRINT("Timeout.");
					break;
				case nn::socket::ENETDOWN:
					GLF_NETINTEFRFACE_VERBOSE_PRINT("The local network interface is down.");
					break;
			};

			return readVal > 0;
#else
			fd_set fds;
			FD_ZERO(&fds);
			FD_SET(sock, &fds);
			struct timeval tv = { 0, 0 };

#ifdef WIN32

			// the first arg to select in win32 is ignored.
			// It's included only for compatibility with Berkeley sockets.
			int ret = select(1, &fds, NULL, NULL, &tv);

#elif defined(GLF_PLATFORM_PS3)

			int ret = socketselect(sock + 1, &fds, NULL, NULL, &tv);

#elif defined(GLF_PLATFORM_PSP2)

			char b;
			int ret = sceNetRecv(sock, &b, 1, SCE_NET_MSG_PEEK | SCE_NET_MSG_DONTWAIT);

			if(ret < 0 && ret != SCE_NET_ERROR_EAGAIN)
			{
				mSocket.mLastError = NETERROR_UNKNOWN;
				return false;
			}
			return ret > 0;

#else

			// It should be the value of the highest numbered FD within any of the passed fd_sets,
			// plus one... Because, the max FD value + 1 == the number of FDs
			// that select() must concern itself with, from within the passed fd_sets...)
			int ret = select(sock + 1, &fds, NULL, NULL, &tv);

#endif

			if (ret == 0)
			{
				return false;
			}
			else if (ret < 0)
			{
				mSocket.mLastError = NETERROR_UNKNOWN;
				return false;
			}

			if (FD_ISSET(sock, &fds))
			{
				// Socket has data available.
				return true;
			}

			return false;
#endif
		}

	};

}
